package com.ygl.strategy.opentracing.operation;

import com.google.common.collect.ImmutableMap;
import com.ygl.common.constant.DiscoveryConstant;
import com.ygl.core.adapter.CoreAdapter;
import com.ygl.strategy.context.StrategyContextHolder;
import com.ygl.strategy.opentracing.constant.StrategyOpentracingConstant;
import com.ygl.strategy.opentracing.context.StrategyOpentracingContext;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.tag.Tags;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.Map;

/**
 * @Author yanggld
 * @Date 2019/12/19-16:52
 * @Description
 */
@Slf4j
public class StrategyOpentracingOperation {


	@Autowired
	protected CoreAdapter coreAdapter;

	@Autowired
	protected StrategyContextHolder strategyContextHolder;

	@Autowired
	private Tracer tracer;

	@Value("${" + StrategyOpentracingConstant.SPRING_APPLICATION_STRATEGY_TRACE_OPENTRACING_ENABLED + ":false}")
	protected Boolean traceOpentracingEnabled;

	@Value("${" + StrategyOpentracingConstant.SPRING_APPLICATION_STRATEGY_TRACE_OPENTRACING_SEPARATE_SPAN_ENABLED + ":true}")
	protected Boolean traceOpentracingSeparateSpanEnabled;

	public void opentracingInitialize() {
		if (!traceOpentracingEnabled) {
			return;
		}

		if (!traceOpentracingSeparateSpanEnabled) {
			return;
		}

		Span span = tracer.buildSpan(DiscoveryConstant.SPAN_VALUE).start();
		StrategyOpentracingContext.getCurrentContext().setSpan(span);

		log.debug("Trace chain for Opentracing initialized...");
	}

	public void opentracingHeader(Map<String, String> customizationMap) {
		if (!traceOpentracingEnabled) {
			return;
		}

		Span span = getCurrentSpan();
		if (span == null) {
			log.error("Span not found in context to opentracing header");

			return;
		}

		if (MapUtils.isNotEmpty(customizationMap)) {
			for (Map.Entry<String, String> entry : customizationMap.entrySet()) {
				span.setTag(entry.getKey(), entry.getValue());
			}
		}
		// 以独立的Span节点输出
		if (traceOpentracingSeparateSpanEnabled) {
			span.setTag(Tags.COMPONENT.getKey(), DiscoveryConstant.TAG_COMPONENT_VALUE);
		}
		span.setTag(DiscoveryConstant.TRACE_ID, span.context().toTraceId());
		span.setTag(DiscoveryConstant.SPAN_ID, span.context().toSpanId());
		span.setTag(DiscoveryConstant.N_D_SERVICE_GROUP, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_GROUP));
		span.setTag(DiscoveryConstant.N_D_SERVICE_TYPE, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_TYPE));
		span.setTag(DiscoveryConstant.N_D_SERVICE_ID, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_ID));
		span.setTag(DiscoveryConstant.N_D_SERVICE_ADDRESS, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_ADDRESS));
		span.setTag(DiscoveryConstant.N_D_SERVICE_VERSION, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION));
		span.setTag(DiscoveryConstant.N_D_SERVICE_REGION, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_REGION));
		span.setTag(DiscoveryConstant.N_D_SERVICE_ENVIRONMENT, strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_ENVIRONMENT));

		String routeVersion = strategyContextHolder.getHeader(DiscoveryConstant.N_D_VERSION);
		if (StringUtils.isNotEmpty(routeVersion)) {
			span.setTag(DiscoveryConstant.N_D_VERSION, routeVersion);
		}
		String routeRegion = strategyContextHolder.getHeader(DiscoveryConstant.N_D_REGION);
		if (StringUtils.isNotEmpty(routeRegion)) {
			span.setTag(DiscoveryConstant.N_D_REGION, routeRegion);
		}
		String routeVersionWeight = strategyContextHolder.getHeader(DiscoveryConstant.N_D_VERSION_WEIGHT);
		if (StringUtils.isNotEmpty(routeVersionWeight)) {
			span.setTag(DiscoveryConstant.N_D_VERSION_WEIGHT, routeVersionWeight);
		}
		String routeRegionWeight = strategyContextHolder.getHeader(DiscoveryConstant.N_D_REGION_WEIGHT);
		if (StringUtils.isNotEmpty(routeRegionWeight)) {
			span.setTag(DiscoveryConstant.N_D_REGION_WEIGHT, routeRegionWeight);
		}

		log.debug("Trace chain information outputs to Opentracing...");
	}

	public void opentracingLocal(String className, String methodName, Map<String, String> customizationMap) {
		if (!traceOpentracingEnabled) {
			return;
		}

		Span span = getCurrentSpan();
		if (span == null) {
			log.error("Span not found in context to opentracing local");

			return;
		}

		if (MapUtils.isNotEmpty(customizationMap)) {
			for (Map.Entry<String, String> entry : customizationMap.entrySet()) {
				span.setTag(entry.getKey(), entry.getValue());
			}
		}
		// 以独立的Span节点输出
		if (traceOpentracingSeparateSpanEnabled) {
			span.setTag(Tags.COMPONENT.getKey(), DiscoveryConstant.TAG_COMPONENT_VALUE);
		}
		span.setTag(DiscoveryConstant.CLASS, className);
		span.setTag(DiscoveryConstant.METHOD, methodName);
		span.setTag(DiscoveryConstant.TRACE_ID, span.context().toTraceId());
		span.setTag(DiscoveryConstant.SPAN_ID, span.context().toSpanId());
		span.setTag(DiscoveryConstant.N_D_SERVICE_GROUP, coreAdapter.getGroup());
		span.setTag(DiscoveryConstant.N_D_SERVICE_ID, coreAdapter.getServiceId());
		span.setTag(DiscoveryConstant.N_D_SERVICE_ADDRESS, coreAdapter.getHost() + ":" + coreAdapter.getPort());
		span.setTag(DiscoveryConstant.N_D_SERVICE_VERSION, coreAdapter.getVersion());
		span.setTag(DiscoveryConstant.N_D_SERVICE_REGION, coreAdapter.getRegion());
		span.setTag(DiscoveryConstant.N_D_SERVICE_ENVIRONMENT, coreAdapter.getEnvironment());

		String routeVersion = strategyContextHolder.getHeader(DiscoveryConstant.N_D_VERSION);
		if (StringUtils.isNotEmpty(routeVersion)) {
			span.setTag(DiscoveryConstant.N_D_VERSION, routeVersion);
		}
		String routeRegion = strategyContextHolder.getHeader(DiscoveryConstant.N_D_REGION);
		if (StringUtils.isNotEmpty(routeRegion)) {
			span.setTag(DiscoveryConstant.N_D_REGION, routeRegion);
		}
		String routeVersionWeight = strategyContextHolder.getHeader(DiscoveryConstant.N_D_VERSION_WEIGHT);
		if (StringUtils.isNotEmpty(routeVersionWeight)) {
			span.setTag(DiscoveryConstant.N_D_VERSION_WEIGHT, routeVersionWeight);
		}
		String routeRegionWeight = strategyContextHolder.getHeader(DiscoveryConstant.N_D_REGION_WEIGHT);
		if (StringUtils.isNotEmpty(routeRegionWeight)) {
			span.setTag(DiscoveryConstant.N_D_REGION_WEIGHT, routeRegionWeight);
		}

		log.debug("Trace chain information outputs to Opentracing...");
	}

	public void opentracingError(String className, String methodName, Throwable e) {
		if (!traceOpentracingEnabled) {
			return;
		}

		if (!traceOpentracingSeparateSpanEnabled) {
			return;
		}

		Span span = getCurrentSpan();
		if (span == null) {
			log.error("Span not found in context to opentracing error");

			return;
		}

		span.log(new ImmutableMap.Builder<String, Object>()
				.put(DiscoveryConstant.CLASS, className)
				.put(DiscoveryConstant.METHOD, methodName)
				.put(DiscoveryConstant.EVENT, Tags.ERROR.getKey())
				.put(DiscoveryConstant.ERROR_OBJECT, e)
				.build());

		log.debug("Trace chain error outputs to Opentracing...");
	}

	public void opentracingClear() {
		if (!traceOpentracingEnabled) {
			return;
		}

		if (!traceOpentracingSeparateSpanEnabled) {
			return;
		}

		Span span = getCurrentSpan();
		if (span != null) {
			span.finish();
		} else {
			log.error("Span not found in context to opentracing clear");
		}
		StrategyOpentracingContext.clearCurrentContext();

		log.debug("Trace chain context of Opentracing cleared...");
	}

	public Span getCurrentSpan() {
		return traceOpentracingSeparateSpanEnabled ? StrategyOpentracingContext.getCurrentContext().getSpan() : tracer.activeSpan();
	}

	public String getTraceId() {
		if (!traceOpentracingEnabled) {
			return null;
		}

		Span span = tracer.activeSpan();
		if (span != null) {
			return span.context().toTraceId();
		}

		return null;
	}

	public String getSpanId() {
		if (!traceOpentracingEnabled) {
			return null;
		}

		Span span = tracer.activeSpan();
		if (span != null) {
			return span.context().toSpanId();
		}

		return null;
	}
}
